#pragma once
#ifndef __GLF_DEBUGGER_PROFILER_H__
#define __GLF_DEBUGGER_PROFILER_H__

#include <glf/config.h>
#include <glf/config/platform.h>

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
#   include <libperf.h>
#   include <sdk_version.h> // Needed to detect API version as it changed at 0.996...
#endif

#if GLF_ENABLE_DEBUGGER

#include <glf/debugger/debugger.h>
#include <vector>

#endif 

namespace glf
{
namespace debugger
{

#if GLF_ENABLE_DEBUGGER

class StringAllocator
{
public:
	StringAllocator() : 
		mCurrentSizeAvailable(0)
	{
	}
	~StringAllocator()
	{
		Free();
	}
	const char* Allocate(const char* str);
	void Free();

private:
	std::vector<char*, DebuggerAllocator<char*> > mBlocks;
	int mCurrentSizeAvailable;
};

class EventType
{
	static bool sDefaultMode;
public:
	// EventType should be global/static
	
	static bool GetDefaultMode() { return sDefaultMode; }
	static void SetDefaultMode(bool mode) { sDefaultMode = mode; }
	
	EventType(const char* context, bool enabled = sDefaultMode);

	bool IsEnabled() const { return mEnabled; }
	void SetEnabled(bool enabled) { mEnabled = enabled; }


private:
	stringdbg mContext;
	bool mEnabled;
	uint mID;

	friend class Profiler;
};

extern EventType sDefaultEventType;
extern EventType sConditionEventType;
extern EventType sMutexEventType;
extern EventType sSemaphoreEventType;
extern EventType sThreadEventType;
extern EventType sStreamEventType;

class Profiler : public Module
{
	GLF_DECLARE_SINGLETON_PLACEMENT_NEW(Profiler)
public:

	class Event
	{
	public:
		Event(const char* name, const char* desc = NULL, bool duplicateName = true) :
			mName(name),
			mDesc(desc),
			mDuplicateName(duplicateName),
			mTimeEvent(false)
		{
		}
		const char* mName;
		const char* mDesc;
		bool mDuplicateName;
		bool mTimeEvent;
	};

	class StoredEvent
	{
	public:
		uint64 mBegin;
		uint64 mEnd;
		uint mDepth;
		bool mTimeEvent;
		const char* mName;
		const char* mDesc;
	};

	static const int EVENT_BLOCK_CAPACITY = 2048;

	struct EventPool
	{
		StoredEvent mEvents[EVENT_BLOCK_CAPACITY];	
		uint mSize;
	};

	struct EventCollector
	{
		EventCollector() :
			mEventStackIndex(0)
		{
		}

		~EventCollector()
		{
			FreeEvents();
		}

		StoredEvent* AllocateEvent();
		uint GetEventCount();
		void FreeEvents();

		static void* operator new(size_t size)
		{
			return Alloc(size);
		}

		static void operator delete(void *p)
		{
			Free(p);
		}

		static const uint MAX_STACK_SIZE = 256;

		StoredEvent* mEventStack[MAX_STACK_SIZE];
		int mEventStackIndex;
		std::vector<EventPool*, DebuggerAllocator<EventPool*> > mEventPools;
	};

	struct ThreadInfos
	{
		int mPriority;
		bool mIsNameSet;
		stringdbg mName;
		EventCollector mCollector;
		StringAllocator mStringAllocator;
	};

	typedef void (*BeginEventCallback)(const char* name);
	typedef void (*EndEventCallback)();

	void Start();
	void Stop();
	bool IsEnabled() { return mEnabled; }

	void BeginEvent(const Event& event);
	void EndEvent();
	void AddTimeEvent(const Event& event, const EventType& eventType = sDefaultEventType);
	void RegisterEventCallbacks(BeginEventCallback begin, EndEventCallback end);
	void RegisterEventType(EventType& eventType);

	void SetEnableAllEventType(bool value=true);

	virtual ~Profiler();

	virtual void UpdateFrame();
	virtual void Parse(int type, PacketReader& in);
	virtual void ConnectionOpened();
	virtual void ConnectionClosed();

private:
	Profiler();
	

	enum MessageType
	{
		// Debugger to Glf
		MESSAGE_START,
		MESSAGE_STOP,
		MESSAGE_SET_EVENT_TYPES,

		// Glf to Debugger
		MESSAGE_BEGIN_EVENTS,
		MESSAGE_FRAME_EVENTS,
		MESSAGE_THREAD_EVENTS,
		MESSAGE_END_EVENTS,
		MESSAGE_EVENT_TYPES
	};

	enum CrtDbgFrequencyType
	{
		CRTDBG_NEVER,
		CRTDBG_FRAME,
		CRTDBG_SCOPE_EVENTS
	};

	void ClearProfilerCollectors();
	void SendEvents();
	void SendEventTypes();
	void SetCrtDbgFrequency(int type) { mCrtDbgFrequencyType = (CrtDbgFrequencyType)type; }

	ThreadInfos mThreadInfos[GLF_DEBUGGER_MAXIMUM_THREAD_COUNT];
	int mMainThreadID;
	bool mEnabled;
	CrtDbgFrequencyType mCrtDbgFrequencyType;
	std::vector<EventCollector*, DebuggerAllocator<EventCollector*> > mProfilerEventCollectors;
	BeginEventCallback mBeginEventCallback;
	EndEventCallback mEndEventCallback;

	typedef std::map<stringdbg, EventType*, std::less<stringdbg>, DebuggerAllocator<std::pair<const stringdbg, EventType*> > > EventTypesMap;
	EventTypesMap mEventTypesMap;
	std::vector<EventType*, DebuggerAllocator<EventType*> > mEventTypes;
	std::vector<EventType*, DebuggerAllocator<EventType*> > mSlaveEventTypes;
	bool mEventTypesChanged;

	friend class ScopeEvent;
	friend class MemoryMonitor;
};

#else // GLF_ENABLE_DEBUGGER

class EventType
{
	static bool sDefaultMode;
public:
	static bool GetDefaultMode() { return sDefaultMode; }
	static void SetDefaultMode(bool mode) { sDefaultMode = mode; }

	EventType(const char* context, bool enabled = sDefaultMode)
	{
	}

	bool IsEnabled() const { return false; }
};

extern EventType sDefaultEventType;

#endif // GLF_ENABLE_DEBUGGER

class ScopeEvent
{
public:
	ScopeEvent(const char* name, const char* desc = NULL, const EventType& eventType = sDefaultEventType) :
		mEnabled(eventType.IsEnabled()
#if GLF_ENABLE_DEBUGGER
			&& Profiler::GetInstance() != 0
#endif
			)
	{
		if(!mEnabled) return;

#if GLF_ENABLE_DEBUGGER
		Profiler::Event event(name, desc, false);
		Profiler::GetInstance()->BeginEvent(event);
#endif

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
#   if SCE_PSP2_SDK_VERSION < 0x00996070
        sceRazorCpuPushMarker(name);
#   else
        // Ok, now that's a strange API change, if we want to set a normal 
        // marker, we have to set a marker with HUD, but with HUD disabled O_o
		sceRazorCpuPushMarkerWithHud(name, 0, SCE_RAZOR_MARKER_DISABLE_HUD);
#   endif
#endif
	}

	ScopeEvent(char* name, const char* desc = NULL, const EventType& eventType = sDefaultEventType) :
		mEnabled(eventType.IsEnabled()
#if GLF_ENABLE_DEBUGGER
			&& Profiler::GetInstance() != 0
#endif
			)
	{
		if(!mEnabled) return;

#if GLF_ENABLE_DEBUGGER
		Profiler::Event event(name, desc);
		Profiler::GetInstance()->BeginEvent(event);
#endif

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
#   if SCE_PSP2_SDK_VERSION < 0x00996070
        sceRazorCpuPushMarker(name);
#   else
        // Ok, now that's a strange API change, if we want to set a normal 
        // marker, we have to set a marker with HUD, but with HUD disabled O_o
		sceRazorCpuPushMarkerWithHud(name, 0, SCE_RAZOR_MARKER_DISABLE_HUD);
#   endif
#endif
	}
	
	~ScopeEvent()
	{
		if(!mEnabled) return;

#if GLF_ENABLE_DEBUGGER
		Profiler::GetInstance()->EndEvent();
#endif

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
		sceRazorCpuPopMarker();
#endif
	}

private:
	const bool mEnabled;
};

class VScopeEvent
{
public:
	VScopeEvent(const char* name, ...);
	
	~VScopeEvent()
	{
#if GLF_ENABLE_DEBUGGER
		Profiler::GetInstance()->EndEvent();
#endif

#if GLF_PLATFORM_PSP2 && GLF_PSP2_ENABLE_PERF
		sceRazorCpuPopMarker();
#endif
	}

private:
#if GLF_ENABLE_DEBUGGER
	Profiler* mProfiler;
#endif
};

class SynchronizationScopeEvent
{
public:
	SynchronizationScopeEvent(const EventType& eventType, const char* name, int address) :
		mEnabled(eventType.IsEnabled())
	{
#if GLF_ENABLE_DEBUGGER
		if(mEnabled)
		{
			char descBuffer[64];
			Sprintf_s(descBuffer, "0x%X", address);

			Profiler::Event event(name, descBuffer);
			Profiler::GetInstance()->BeginEvent(event);
		}
#endif
	}
	
	~SynchronizationScopeEvent()
	{
#if GLF_ENABLE_DEBUGGER
		if(mEnabled)
		{
			Profiler::GetInstance()->EndEvent();
		}
#endif
	}

private:
	const bool mEnabled;
};

class StreamScopeEvent
{
public:
	StreamScopeEvent(const EventType& eventType, const char* name, const char* desc = NULL) :
		mEnabled(eventType.IsEnabled())
	{
#if GLF_ENABLE_DEBUGGER
		if(mEnabled)
		{
			Profiler::Event event(name, desc);
			Profiler::GetInstance()->BeginEvent(event);
		}
#endif
	}

	~StreamScopeEvent()
	{
#if GLF_ENABLE_DEBUGGER
		if(mEnabled)
		{
			Profiler::GetInstance()->EndEvent();
		}
#endif
	}

private:
	const bool mEnabled;
};

} // end namespace debugger
} // end namespace glf



#endif
